package to

import (
	"TODecider/src/algorithm"
	"TODecider/src/comnet"
	"TODecider/src/logger"
	"TODecider/src/pb"
	"TODecider/src/structs"
	"TODecider/src/util"
	"strings"
)

const (
	AlgoKM = "km"
	AlgoGS = "gs"
	AlgoGr = "greedy"
	AlgoRa = "random"
)

var (
	log  = logger.GetLogger()
	algo algorithm.Algo
)

func InitAlgorithm(algoName string) {
	algoName = strings.ToLower(algoName)
	switch algoName {
	case AlgoKM:
		algo = algorithm.Algo(new(algorithm.KM))
	case AlgoGS:
		algo = algorithm.Algo(new(algorithm.GS))
	case AlgoGr:
		algo = algorithm.Algo(new(algorithm.Greedy))
	case AlgoRa:
		algo = algorithm.Algo(new(algorithm.Random))
	default:
		log.Panicf("invalid input algorithm name: %s", algoName)
	}
}

func Execute(taskInBasic []*pb.Basic, taskInLatency []*pb.Latency) error {
	return execute(taskInBasic, taskInLatency)
}

/* execute task offloading decision */
func execute(taskBasics []*pb.Basic, taskLatencies []*pb.Latency) error {
	log.Debugf("[TO] decision recv: %v", taskBasics)
	// pre-pend
	comNet, err := comnet.PullRecentComNet()
	if err != nil {
		return err
	}
	nodes := comNet.Nodes
	links := comNet.Links
	M := len(taskBasics)
	N := len(nodes)
	// get core
	pairMap := getEdges(taskBasics, links)
	wbg := getWBG(pairMap, M, N)
	log.Debugf("[TO] wbg: %v", wbg)
	// offloading (KM)
	decisionMap := algo.Match(wbg)
	reformatDecisionMap(decisionMap, M, N)
	// post-pend
	for i, taskBasic := range taskBasics {
		deadline := TypeTaskParamsDict[taskBasic.TaskType].Deadline
		src := pairMap[taskBasic][0].Src
		dest := decisionMap[src]
		offloadTo := comnet.Decode(dest)
		lcDelay := -wbg.Graph[src][src]
		// generate timestamp
		taskBasic.OffloadTo = offloadTo
		taskLatencies[i].Deadline = deadline
		taskLatencies[i].LocalDelay = int32(lcDelay)
	}
	log.Debugf("[TO] decision result: <Basic> %v, <Latency> %v", taskBasics, taskLatencies)
	return nil
}

func getEdges(taskBasics []*pb.Basic, links []comnet.Link) map[*pb.Basic][]structs.WeightedEdge {
	pairMap := make(map[*pb.Basic][]structs.WeightedEdge)
	feed := 0
	// get taskBasic-pair map
	for _, taskBasic := range taskBasics {
		taskArrivalIP := taskBasic.ArriveAt
		dataSize := TypeTaskParamsDict[taskBasic.TaskType].DataSize
		ProDelay := float64(TypeTaskParamsDict[taskBasic.TaskType].Deadline / 2)
		// judge arrival
		arrival, ok := comnet.NodeIPMap[taskArrivalIP]
		if !ok {
			log.Panicf("[TO] invalid node string: %s (NON-EXIST IN COMNET!)", taskArrivalIP)
		}
		// offload
		for _, link := range links {
			if link.NodeFrom == taskArrivalIP {
				nodeTo := link.NodeTo
				node := comnet.NodeIPMap[nodeTo]
				UpDelay := float64(dataSize) / link.Rate
				pair := genEdge(taskBasic, node, feed, UpDelay+ProDelay+link.EsDelay)
				pairMap[taskBasic] = append(pairMap[taskBasic], pair)
			}
		}
		// local
		pair := genEdge(taskBasic, arrival, feed, ProDelay)
		pairMap[taskBasic] = append(pairMap[taskBasic], pair)
		feed++
	}
	return pairMap
}

func genEdge(taskIn *pb.Basic, node comnet.Node, index int, Cost0 float64) structs.WeightedEdge {
	CGDelay := predExecutable(TypeTaskParamsDict[taskIn.TaskType].Weight, node)
	return structs.WeightedEdge{
		Src:  index,
		Des:  comnet.Encode(node.Ip),
		Cost: CGDelay + Cost0,
	}
}

func getWBG(pairMap map[*pb.Basic][]structs.WeightedEdge, M int, N int) structs.WBG {
	var wbg structs.WBG
	E := N
	if M > N {
		/* Min-Span */
		E = N * ((M-1)/N + 1)
		/* Max-Span */
		//E = M * N
	}
	graph := util.MakeSlicesFloat64(E, E)
	// get bi-part graph
	for _, pairs := range pairMap {
		for _, pair := range pairs {
			graph[pair.Src][pair.Des] = -pair.Cost
		}
	}
	if M > N {
		for m := 0; m < M; m++ {
			for n := N; n < E; n++ {
				graph[m][n] = graph[m][n-N*(n/N)]
			}
		}
	}
	wbg.Graph = graph
	return wbg
}

func reformatDecisionMap(decisionMap map[int]int, M int, N int) {
	if M > N {
		for m, n := range decisionMap {
			decisionMap[m] = n - N*(n/N)
		}
	}
}
